package com.ss.android.common.load;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import com.bytedance.common.utility.Logger;
import com.bytedance.common.utility.b.ThreadPlusThreadFactory;
import com.bytedance.common.utility.collection.WeakHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

abstract class AbsAsyncLoader<K, T, E, V, R, C, N extends AbsNode<K, T, E, V, R, C, N>> implements WeakHandler.Callback {
    final class Task implements Runnable {
        N node = null;
        int workTag;

        public final void run() {

            R result = null;
            if (this.node != null) {
                try {
                    result = AbsAsyncLoader.this.doInBackground(this.node.key, this.node.param, this.node.extra);
                } catch (Exception e) {

                }
                this.node.data = result;
                Message message = AbsAsyncLoader.this.mWeakHandler.obtainMessage(1001);
                message.obj = this;
                message.arg1 = this.workTag;
                mWeakHandler.sendMessage(message);
            } else {
                Message message = mWeakHandler.obtainMessage(1002);
                message.obj = this;
                mWeakHandler.sendMessage(message);
            }
        }
    }

    final Handler mWeakHandler;
    private static final ExecutorService CACHED_EXECUTOR = Executors.newCachedThreadPool(
            new ThreadPlusThreadFactory("AsyncLoader-Worker", true));
    private static AtomicInteger mInstCount = new AtomicInteger();
    private int mCapacity;
    private Object mLock = new Object();
    private final HashMap<K, N> mRunningNode;
    private N mHead;
    private N mTail;
    private volatile boolean mInited;
    private final AtomicBoolean mStoped;
    private final AtomicBoolean mPaused;
    private volatile int mWorkTag;
    private final LinkedList<AbsAsyncLoader<K, T, E, V, R, C, N>.Task> mScrapTasks;
    private ArrayList<N> mScrapNodes;

    protected AbsAsyncLoader(int capacity, int maxWorker) {
        this(capacity, maxWorker, false);
    }

    protected AbsAsyncLoader(int capacity, int maxWorker, boolean useMainLooper) {
        this.mInited = false;
        this.mScrapTasks = new LinkedList();
        this.mScrapNodes = new ArrayList();
        if (maxWorker <= 0) {
            throw new IllegalArgumentException("maxWorker must be great than 1");
        }

        if (capacity <= maxWorker) {
            capacity = maxWorker + 1;
        }

        this.mCapacity = capacity;
        this.mWorkTag = 1;
        this.mStoped = new AtomicBoolean();
        this.mPaused = new AtomicBoolean();
        this.mRunningNode = new HashMap();
        if (!this.mInited) {
            this.mHead = this.getNode();
            this.mTail = this.getNode();
            this.mHead.next = this.mTail;
            this.mTail.prev = this.mHead;
            this.mInited = true;
        }

        if ((useMainLooper) || Looper.myLooper() == null) {
            this.mWeakHandler = new WeakHandler(Looper.getMainLooper(), this);
            if (!useMainLooper) {
                Logger.w("No looper for this thread, use MainLooper as default.");
            }
        } else {
            this.mWeakHandler = new WeakHandler(this);
        }

        for (int i = 0; i < maxWorker; i++) {
            this.mScrapTasks.add(new Task());
        }
        Logger.d("AbsAsyncLoader", "new instance " + mInstCount.incrementAndGet());
    }

    private static void detach(AbsNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private static void attach(AbsNode pre, AbsNode newNode) {
        newNode.next = pre.next;
        newNode.prev = pre;
        newNode.next.prev = newNode;
        pre.next = newNode;
    }

    protected abstract N makeNode();

    protected abstract R doInBackground(K key, T param, E extra);

    public void cancelTask(K key, V obj) {
        if (key != null) {
            synchronized (this.mLock) {
                if (this.mStoped.get()) return;
                N node = this.mRunningNode.get(key);
                if (node == null) {
                    return;
                } else {
                    node.unbindObj(obj);
                    if (!node.isEmpty()) return;
                    if (node.next != null && node.prev != null) {
                        detach(node);
                        this.mRunningNode.remove(key);
                        this.recycleNode(node);
                    }
                }
            }
        }
    }

    public void addTask(K key, T param, E extra, V obj) {
        if (key != null) {
            synchronized (this.mLock) {
                if (this.mStoped.get()) {
                    Logger.w("AbsAsyncLoader", "This loader is stoped already");
                    return;
                }
                N node = this.mRunningNode.get(key);
                if (node != null) {
                    if (obj != null) {
                        node.bindObj(obj);
                    }
                    if (node.next != null && node.prev != null && node.prev != this.mHead) {
                        detach(node);
                        attach(this.mHead, node);
                    }
                } else {
                    node = this.getNode();
                    node.key = key;
                    node.param = param;
                    if (obj != null) {
                        node.bindObj(obj);
                    }
                    node.extra = extra;
                    attach(this.mHead, node);
                    this.mRunningNode.put(key, node);
                    if (this.mRunningNode.size() > this.mCapacity) {
                        this.mRunningNode.remove(this.mTail.prev.key);
                        node = this.mTail.prev;
                        detach(this.mTail.prev);
                        this.recycleNode(node);
                    }
                }
                this.trySubmitTask();
            }
        }
    }

    protected abstract void onLoaded(K key, T param, E extra, C obj, R result);

    public boolean isInQueue(String key) {
        synchronized (this.mLock) {
            if (this.mStoped.get()) {
                Logger.w("AbsAsyncLoader", "This loader is stoped already");
                return true;
            } else {
                if (this.mRunningNode.get(key) != null) {
                    return true;
                } else {
                    return false;
                }
            }
        }
    }

    private void recycleNode(N node) {
        if (node != null) {
            node.key = null;
            node.prev = null;
            node.next = null;
            node.extra = null;
            node.param = null;
            node.data = null;
            node.clearObj();
            if (this.mScrapNodes.size() < 50) {
                this.mScrapNodes.add(node);
            }
        }
    }

    public void clearQueue() {
        synchronized (this.mLock) {
            while (this.mHead.next != this.mTail) {
                if (this.mHead.next == null) {
                    break;
                }

                if (this.mHead.next == null) {
                    continue;
                }

                this.mHead.next.clearObj();
                this.mRunningNode.remove(this.mHead.next.key);
                this.mHead.next = this.mHead.next.next;
            }

            this.mHead.next = this.mTail;
            this.mTail.prev = this.mHead;
        }
    }

    public void stop() {
        synchronized (this.mLock) {
            if (!this.mStoped.get()) {
                mInstCount.decrementAndGet();
                this.mStoped.set(true);
                Iterator<N> iterator = this.mRunningNode.values().iterator();
                while (iterator.hasNext()) {
                    iterator.next().clearObj();
                }
                this.mRunningNode.clear();
                this.mHead.next = this.mTail;
                this.mTail.prev = this.mHead;
            }
        }
        this.mWeakHandler.removeMessages(1001);
    }

    public void pause() {
        synchronized (this.mLock) {
            this.mPaused.set(true);
        }
    }

    public void resume() {
        synchronized (this.mLock) {
            if (this.mStoped.get()) {
                Logger.w("AbsAsyncLoader", "This loader is stoped already");
            } else if (this.mPaused.get()) {
                this.mPaused.set(false);
                this.trySubmitTask();
            }
        }
    }

    private N getNode() {
        N node;
        int size = this.mScrapNodes.size();
        if (size <= 0) {
            node = this.makeNode();
        } else {
            node = this.mScrapNodes.remove(size - 1);
        }
        return node;
    }

    private void trySubmitTask() {
        synchronized (this.mLock) {
            while (true) {
                if (this.mStoped.get()) return;
                if (this.mPaused.get()) return;
                if (this.mHead.next == this.mTail) return;
                try {
                    Task task = this.mScrapTasks.poll();
                    if (task == null) {
                        return;
                    }
                    N node = null;
                    if (!this.mStoped.get() && !this.mPaused.get() && this.mHead.next != this.mTail) {
                        node = this.mHead.next;
                        detach(node);
                        node.next = null;
                        node.prev = null;
                    }
                    if (node == null) {
                        this.mScrapTasks.add(task);
                        return;
                    } else {
                        task.workTag = this.mWorkTag;
                        task.node = node;
                        CACHED_EXECUTOR.submit(task);
                        continue;
                    }
                } catch (Throwable throwable) {
                    continue;
                }
            }
        }
    }

    @Override
    public void handleMsg(Message message) {
        if (message.what == 1001 || message.what == 1002) {
            Task task = (Task) message.obj;
            if (task != null) {
                synchronized (this.mLock) {
                    N node = null;
                    if (message.what == 1001) {
                        node = task.node;
                    }
                    task.node = null;
                    task.workTag = 0;
                    this.mScrapTasks.add(task);
                    if (node != null) {
                        if (!this.mStoped.get() && message.arg1 == this.mWorkTag) {
                            this.mRunningNode.remove(node.key);
                        }

                        this.onLoaded(node.key, node.param, node.extra, node.getObj(), node.data);
                        this.recycleNode(node);
                    }
                }
                this.trySubmitTask();
            }
        }
    }
}

